﻿''' <summary>
''' MPG SlidingButtonStrip.  Note: This control automatically scales based on DPI.  The base designer DPI is ALWAYS 192 (VGA/WVGA).  
''' Make sure your iPrettListItems scale down to QVGA and square devices when rendering their content.  You can get the scaling factor
''' by calling the ReadOnly ActualScaleFactor property.
''' </summary>
Public Class SlidingButtonStrip
  Private _items As IEnumerable(Of iSlidingButtonStripItem)
  Private _left As Integer = 0
  Private _highlightCenter As Integer = 0
  Private _totalItemsWidth As Integer = 0
  Private _selectedItem As iSlidingButtonStripItem = Nothing
  Private _lastSlideItem As iSlidingButtonStripItem = Nothing

  ' Scrolling, graphics, and clicking variables
  Private _dpiFactor As Single = 0.0F
  Private _buttonWidth As Integer = 100
  Private _mouseIsDown As Boolean = False
  Private _allowedToSlide As Boolean = False
  Private _selectedButtonBackground As Bitmap = Nothing

  Public Event SlidingItemChanged()

  Public Sub New()
    InitializeComponent()
    Me._dpiFactor = Me.AutoScaleDimensions.Height / 192.0F
  End Sub

  ''' <summary>
  ''' The special background bitmap for the currently selected (or sliding over) button.  If set to nothing, this is not used.  The image
  ''' should be UNSCALED in 192 DPI (VGA/WVGA).  The class will automatically scale the image to the correct size when set here.  This
  ''' property always makes a copy of the bitmap, so you should dispose of the bitmap you are passing in when you are finished with it.
  ''' For best quality, the size of the bitmap should be the ImageWidth property (NOT ActualImageWidth) x the unscaled height of the 
  ''' control at 192 DPI (SlidingButtonStrip.Height / SlidingButtonStrip.ActualScaleFactor)
  ''' </summary>
  Public Property SelectedButtonBackground() As Bitmap
    Get
      Return Me._selectedButtonBackground
    End Get
    Set(ByVal value As Bitmap)
      If (value IsNot Nothing) Then
        If Me._selectedButtonBackground IsNot Nothing Then
          Me._selectedButtonBackground.Dispose()
        End If
        Me._selectedButtonBackground = New Bitmap(Me.ActualButtonWidth, Me.Height)
        Dim g As Graphics = Graphics.FromImage(Me._selectedButtonBackground)
        g.DrawImage(value, New Rectangle(0, 0, Me._selectedButtonBackground.Width, Me._selectedButtonBackground.Height), New Rectangle(0, 0, value.Width, value.Height), GraphicsUnit.Pixel)
        g.Dispose()
      End If
    End Set
  End Property

  Public ReadOnly Property ActualScaleFactor()
    Get
      Return Me._dpiFactor
    End Get
  End Property

  Public Property SelectedItem() As iSlidingButtonStripItem
    Get
      Return Me._selectedItem
    End Get
    Set(ByVal value As iSlidingButtonStripItem)
      If value IsNot Nothing AndAlso Me._items.Contains(value) AndAlso value.Visible Then
        Me._selectedItem = value
        Me.SnapToItem(value)
      End If
    End Set
  End Property

  Public Property SelectedItemIndex() As Integer
    Get
      Return Me._items.ToList.IndexOf(Me._selectedItem)
    End Get
    Set(ByVal value As Integer)
      If value >= 0 AndAlso value < Me._items.Count Then
        Dim it As iSlidingButtonStripItem = Me._items(value)
        If it.Visible Then
          Me._selectedItem = it
          Me.SnapToItem(it)
        End If
      End If
    End Set
  End Property

  Public Sub SetSelectedItemIndexNoRaise(ByVal index As Integer)
    If index >= 0 AndAlso index < Me._items.Count Then
      Dim it As iSlidingButtonStripItem = Me._items(index)
      If it.Visible Then
        Me._selectedItem = it
        Me.SnapToItem(it, False)
      End If
    End If
  End Sub

  Public ReadOnly Property LastSlideItem() As iSlidingButtonStripItem
    Get
      Return Me._lastSlideItem
    End Get
  End Property

  Public ReadOnly Property LastSlideItemIndex() As Integer
    Get
      If Me._lastSlideItem IsNot Nothing Then
        Return Me._items.ToList.IndexOf(Me._lastSlideItem)
      Else
        Return Nothing
      End If
    End Get
  End Property


  ''' <summary>
  ''' Width of each button.  Note: Set this value always as if the device is VGA.  It will automatically scale based on DPI.  Use
  ''' ActualScaleFactor() to determine the factor the control is using to scale.
  ''' </summary>
  Public Property ButtonWidth() As Integer
    Get
      Return Me._buttonWidth
    End Get
    Set(ByVal value As Integer)
      Me._buttonWidth = value
    End Set
  End Property

  Public ReadOnly Property ActualButtonWidth() As Integer
    Get
      Return Me._buttonWidth * Me._dpiFactor
    End Get
  End Property




  Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
    Me._mouseIsDown = True
    Me._allowedToSlide = True
    Me._lastSlideItem = Me._selectedItem

    For Each sbsi As iSlidingButtonStripItem In Me._items
      If sbsi.Visible AndAlso sbsi.ClickRectangle.Contains(e.X, e.Y) Then
        sbsi.OnMouseDown()
        If sbsi IsNot Me._selectedItem Then
          Me._allowedToSlide = False
          Me.SnapToItem(sbsi)
        End If
        Exit For
      End If
    Next

    MyBase.OnMouseDown(e)
  End Sub

  Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
    If Me._allowedToSlide Then
      For Each sbsi As iSlidingButtonStripItem In Me._items
        Dim sbsiFullRect As New Rectangle(sbsi.ClickRectangle.Left, -1000000, sbsi.ClickRectangle.Width, 2000000)
        If sbsi.Visible AndAlso sbsiFullRect.Contains(e.X, e.Y) Then
          If sbsi IsNot Me._lastSlideItem Then
            Me._lastSlideItem.OnMouseLeave()
            Me._lastSlideItem = sbsi
            Me._lastSlideItem.OnMouseEnter()
            RaiseEvent SlidingItemChanged()
          End If
          Exit For
        End If
      Next

      Me.CalculatePositions(e.X, True)
    End If

    Me.InvalidateSmart()
    MyBase.OnMouseMove(e)
  End Sub



  Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)
    Dim found As Boolean = False

    Me._mouseIsDown = False

    For Each sbsi As iSlidingButtonStripItem In Me._items
      Dim sbsiFullRect As New Rectangle(sbsi.ClickRectangle.Left, -1000000, sbsi.ClickRectangle.Width, 2000000)
      If sbsi.Visible AndAlso sbsiFullRect.Contains(e.X, e.Y) Then
        sbsi.OnMouseUp()
        If Me._allowedToSlide Then
          Me.SnapToItem(sbsi)
        End If
        found = True
        Exit For
      End If
    Next

    If Not found Then
      Me._lastSlideItem.OnMouseUp()
      If Me._allowedToSlide Then
        Me.SnapToItem(Me._lastSlideItem)
      End If
    End If

    Me._lastSlideItem = Nothing

    If Me._allowedToSlide Then
      RaiseEvent SlidingItemChanged()
    End If

    Me._allowedToSlide = False

    MyBase.OnMouseUp(e)
  End Sub

  Private Sub SnapToItem(ByVal sbsiSnap As iSlidingButtonStripItem, Optional ByVal raiseOnItemSelected As Boolean = True)
    Dim curLeft As Integer = 0

    Me._selectedItem = sbsiSnap

    For Each sbsi As iSlidingButtonStripItem In Me._items
      If sbsi.Visible Then
        If sbsi Is sbsiSnap Then
          Exit For
        End If
        curLeft += Me.ActualButtonWidth
      End If
    Next

    Me.CalculatePositions(curLeft)
    Me.InvalidateSmart()

    If raiseOnItemSelected Then
      sbsiSnap.OnItemSelected()
    End If
  End Sub

  Private Sub CalculatePositions()
    Dim curLeft As Integer = 0

    If Me._selectedItem IsNot Nothing Then
      For Each sbsi As iSlidingButtonStripItem In Me._items
        If sbsi.Visible Then
          If sbsi Is Me._selectedItem Then
            Exit For
          End If
          curLeft += Me.ActualButtonWidth
        End If
      Next
    End If

    Me.CalculatePositions(curLeft)
  End Sub

  Private Sub CalculatePositions(ByVal x As Integer, Optional ByVal isMouseDrag As Boolean = False)
    Dim distance As Single
    Dim count As Integer = 0

    For Each sbsi As iSlidingButtonStripItem In Me._items
      If sbsi.Visible Then
        count += 1
      End If
    Next

    Me._totalItemsWidth = count * Me.ActualButtonWidth

    If isMouseDrag Then
      If Me.Width < Me._totalItemsWidth Then
        If x < Me.ActualButtonWidth / 2 Then
          distance = 0.0F
        ElseIf x > Me.Width - (Me.ActualButtonWidth / 2) Then
          distance = 1.0F
        Else
          distance = CSng(x - (Me.ActualButtonWidth / 2)) / CSng(Me.Width - Me.ActualButtonWidth)
        End If
      Else
        If x < Me.ActualButtonWidth / 2 Then
          distance = 0.0F
        ElseIf x > Me._totalItemsWidth - (Me.ActualButtonWidth / 2) Then
          distance = CSng((Me._totalItemsWidth - (Me.ActualButtonWidth / 2)) - (Me.ActualButtonWidth / 2)) / CSng(Me._totalItemsWidth - Me.ActualButtonWidth)
        Else
          distance = CSng(x - (Me.ActualButtonWidth / 2)) / CSng(Me._totalItemsWidth - Me.ActualButtonWidth)
        End If
      End If
    Else
      If x <= (Me.ActualButtonWidth / 2) + 1 Then
        distance = 0.0F
      ElseIf x >= Me._totalItemsWidth - ((Me.ActualButtonWidth / 2) + 1) Then
        distance = 1.0F
      Else
        distance = CSng(x) / CSng(Me._totalItemsWidth - Me.ActualButtonWidth)
      End If
    End If

    If Me._totalItemsWidth > Me.Width Then
      Me._left = -1 * distance * (Me._totalItemsWidth - Me.Width)
    Else
      Me._left = 0
    End If

    Me._highlightCenter = (distance * CSng(Me._totalItemsWidth - Me.ActualButtonWidth)) + (Me.ActualButtonWidth / 2)
  End Sub



  Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
    Static Dim bufWidth As Integer = 0
    Static Dim bufHeight As Integer = 0
    Static Dim buffer As Bitmap = Nothing
    Static Dim brBg As New SolidBrush(Me.BackColor)

    Dim sbbrRect As New Rectangle ' Rectangle of the selected button background (also used for overlay)

    Dim gWidth As Integer

    If Me._totalItemsWidth < Me.Width Then
      gWidth = Me.Width
    Else
      gWidth = Me._totalItemsWidth
    End If

    If buffer Is Nothing OrElse gWidth <> bufWidth OrElse Me.Height <> bufHeight Then
      bufWidth = gWidth
      bufHeight = Me.Height

      If buffer IsNot Nothing Then
        buffer.Dispose()
      End If

      buffer = New Bitmap(bufWidth, bufHeight)

      ' Recalculate positions, since the size of the control changed
      Me.CalculatePositions()
    End If

    Dim g As Graphics = Graphics.FromImage(buffer)
    g.FillRectangle(brBg, 0, 0, bufWidth, bufHeight)

    If Me.Enabled Then
      ' Calculate where the selected button background is
      sbbrRect.X = Me._highlightCenter - (Me.ActualButtonWidth / 2)
      sbbrRect.Width = Me.ActualButtonWidth
      sbbrRect.Y = 0
      sbbrRect.Height = Me.Height

      If (Me._selectedButtonBackground IsNot Nothing) Then
        g.DrawImage(Me._selectedButtonBackground, sbbrRect, New Rectangle(0, 0, Me._selectedButtonBackground.Width, Me._selectedButtonBackground.Height), GraphicsUnit.Pixel)
      End If

      If Me._items IsNot Nothing Then
        Dim curLeft = 0

        ' Draw the standard images
        For Each sbsi As iSlidingButtonStripItem In Me._items
          If sbsi.Visible Then
            Dim rect As Rectangle
            rect.Y = 0
            rect.X = curLeft
            rect.Height = Me.Height
            rect.Width = Me.ActualButtonWidth

            If Me._mouseIsDown AndAlso Me._allowedToSlide Then
              sbsi.DrawSlidingButtonStripItem(g, rect, sbsi Is Me._lastSlideItem)
            Else
              sbsi.DrawSlidingButtonStripItem(g, rect, sbsi Is Me._selectedItem)
            End If

            rect.X += Me._left
            sbsi.ClickRectangle = rect

            curLeft += Me.ActualButtonWidth
          End If
        Next

        ' Draw the overlay
        curLeft = 0

        For Each sbsi As iSlidingButtonStripItem In Me._items
          If sbsi.Visible Then
            Dim rect As Rectangle
            rect.Y = 0
            rect.X = curLeft
            rect.Height = Me.Height
            rect.Width = Me.ActualButtonWidth

            If Me._mouseIsDown AndAlso Me._allowedToSlide Then
              If sbsi Is Me._lastSlideItem Then
                sbsi.DrawSlidingButtonStripItemOverlay(g, sbbrRect)
              End If
            ElseIf sbsi Is Me._selectedItem Then
              sbsi.DrawSlidingButtonStripItemOverlay(g, sbbrRect)
            End If

            rect.X += Me._left
            curLeft += Me.ActualButtonWidth
          End If
        Next
      End If
    End If

    g.Dispose()
    e.Graphics.DrawImage(buffer, Me._left, 0)
  End Sub

  Protected Overrides Sub OnPaintBackground(ByVal e As System.Windows.Forms.PaintEventArgs)
    'MyBase.OnPaintBackground(e)
  End Sub

  Public Property Items() As IEnumerable(Of iSlidingButtonStripItem)
    Get
      Return Me._items
    End Get
    Set(ByVal value As IEnumerable(Of iSlidingButtonStripItem))
      Me._items = value

      If Me._items IsNot Nothing Then
        For Each sbsi As iSlidingButtonStripItem In Me._items
          If sbsi.Visible Then
            Me.SelectedItem = sbsi
            Exit For
          End If
        Next
      End If

      Me.Refresh()
    End Set
  End Property

  Public Sub ResnapToCurrentIndex()
    If Me._selectedItem IsNot Nothing Then
      Me.SnapToItem(Me._selectedItem, False)
      Me.InvalidateSmart()
    End If
  End Sub

  Public Sub InvalidateSmart()
    Me.TimerInvalidate.Enabled = True
  End Sub

  Private Sub TimerInvalidate_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TimerInvalidate.Tick
    Me.TimerInvalidate.Enabled = False
    Me.Invalidate()
  End Sub

  Protected Overrides Sub OnResize(ByVal e As System.EventArgs)
    Me.Invalidate() ' Can't do a "smart" invalidate since all timers get disabled when the control does
    MyBase.OnResize(e)
  End Sub
End Class
